/*
sha_cpp.cpp:	a C++ version of Linus Torvalds' git SHA1
    Copyright (C) 2008  Daniel Gutson
    
    hamming.cpp is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    This program computes the n-th iteration of the Dijkstra algorithm
    provide in Wikipedia.
    
    For questions, contact me: danielgutson@gmail.com, or visit my
    homepage at http://danielgutson.googlepages.com/professionalinformation

    Based on Linus Torvalds' git SHA1
    (see http://git.kernel.org/?p=git/git.git;a=blob_plain;f=block-sha1/sha1.c;hb=66c9c6c0fbba0894ebce3da572f62eb05162e547),
    experimental to compare C and C++ code generation.
    Build this as: g++ -O3 sha_cpp.cpp -c -o sha_cpp.o
*/

#include <arpa/inet.h>

/* Begin #include "sha1.h" */
typedef struct {
	unsigned int H[5];
	unsigned int W[16];
	unsigned long long size;
} blk_SHA_CTX;

void blk_SHA1_Init(blk_SHA_CTX *ctx);
void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, unsigned long len);
void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx);

#define git_SHA_CTX	blk_SHA_CTX
#define git_SHA1_Init	blk_SHA1_Init
#define git_SHA1_Update	blk_SHA1_Update
#define git_SHA1_Final	blk_SHA1_Final
/* End of sha1.h  */

/* Hash one 64-byte block of data */
extern "C" {
void blk_SHA1Block(blk_SHA_CTX *ctx, const unsigned int *data);
};

#if defined(__i386__) || defined(__x86_64__)

#define SHA_ASM(op, x, n) ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; })
#define SHA_ROL(x,n)	SHA_ASM("rol", x, n)
#define SHA_ROR(x,n)	SHA_ASM("ror", x, n)

#else

#define SHA_ROT(X,l,r)	(((X) << (l)) | ((X) >> (r)))
#define SHA_ROL(X,n)	SHA_ROT(X,n,32-(n))
#define SHA_ROR(X,n)	SHA_ROT(X,32-(n),n)

#endif

template <unsigned int Iteration, unsigned int Max, template <unsigned int> class Operation>
struct Loop
{
    static void loop(int A, int B, int C, int D, int E, const unsigned int *data, unsigned int array[16])
    {
        Operation<Max-Iteration>::operate(A, B, C, D, E, data, array);
        Loop<Iteration-1, Max, Operation>::loop(E, A, B, C, D, data, array);
    }
};

// Stop condition:
template <unsigned int Max, template <unsigned int> class Operation>
struct Loop<0, Max, Operation>
{
    static void loop(int A, int B, int C, int D, int E, const unsigned int *data, unsigned int array[16])
    {}
};

struct NullOperation
{
    static void operate(int A, int B, int C, int D, int E, const unsigned int *data, unsigned int array[16])
    { }
};

template <unsigned int Value, unsigned int Min, unsigned int Max>
struct InRange
{
    enum { value = (Value >= Min && Value  <= Max) };
};

template <bool Condition, class Operation>
struct Conditional
{
    typedef Operation operation;
};

template <class Operation>
struct Conditional<false, Operation>
{
    typedef NullOperation operation;
};

///////////////////////////////////////////////////

#define W(x) (array[(x)&15])
#define setW(x, val) (*(volatile unsigned int *)&W(x) = (val))

/*
 * Where do we get the source from? The first 16 iterations get it from
 * the input data, the next mix it from the 512-bit array.
 */
#define SHA_SRC(t) htonl(data[t])
#define SHA_MIX(t) SHA_ROL(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1)

#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \
	unsigned int TEMP = input(t); setW(t, TEMP); \
	E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \
	B = SHA_ROR(B, 2); } while (0)

template <unsigned int Iteration>
struct Operation_0_15
{
    static void operate(int A, int B, int C, int D, int E, const unsigned int *data, unsigned int array[16])
    {
        SHA_ROUND(Iteration, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E );
    }
};

template <unsigned int Iteration>
struct Operation_16_19
{
    static void operate(int A, int B, int C, int D, int E, const unsigned int *data, unsigned int array[16])
    {
        SHA_ROUND(Iteration, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E );
    }
};

template <unsigned int Iteration>
struct Operation_20_39
{
    static void operate(int A, int B, int C, int D, int E, const unsigned int *data, unsigned int array[16])
    {
        SHA_ROUND(Iteration, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E );
    }
};

template <unsigned int Iteration>
struct Operation_40_59
{
    static void operate(int A, int B, int C, int D, int E, const unsigned int *data, unsigned int array[16])
    {
        SHA_ROUND(Iteration, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E );
    }
};

template <unsigned int Iteration>
struct Operation_60_79
{
    static void operate(int A, int B, int C, int D, int E, const unsigned int *data, unsigned int array[16])
    {
        SHA_ROUND(Iteration, SHA_MIX, (B^C^D) ,  0xca62c1d6, A, B, C, D, E );
    }
};

template <unsigned int Iteration>
struct Oper
{
    static void operate(int A, int B, int C, int D, int E, const unsigned int *data, unsigned int array[16])
    {
        Conditional< InRange<Iteration,  0, 15>::value,  Operation_0_15<Iteration> >::operation::operate(A, B, C, D, E, data, array);
        Conditional< InRange<Iteration, 16, 19>::value, Operation_16_19<Iteration> >::operation::operate(A, B, C, D, E, data, array);
        Conditional< InRange<Iteration, 20, 39>::value, Operation_20_39<Iteration> >::operation::operate(A, B, C, D, E, data, array);
        Conditional< InRange<Iteration, 40, 59>::value, Operation_40_59<Iteration> >::operation::operate(A, B, C, D, E, data, array);
        Conditional< InRange<Iteration, 60, 79>::value, Operation_60_79<Iteration> >::operation::operate(A, B, C, D, E, data, array);
    }
};

void blk_SHA1Block(blk_SHA_CTX *ctx, const unsigned int *data)
{
	unsigned int A,B,C,D,E;
	unsigned int array[16];

	A = ctx->H[0];
	B = ctx->H[1];
	C = ctx->H[2];
	D = ctx->H[3];
	E = ctx->H[4];

    Loop<80, 80, Oper>::loop(A, B, C, D, E, data, array);
}

